/*
 *  Copyright (C) 2005-2007 Renier Roth
 *  www.renier.de
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package de.renier.jkeepass.components;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import de.renier.jkeepass.JKeePass;
import de.renier.jkeepass.elements.BasicElement;
import de.renier.jkeepass.elements.ContainerElement;

/**
 * ContainerElementTreeTransferHandler
 *
 * @author <a href="mailto:software@renier.de">Renier Roth</a>
 */
public class ContainerElementTreeTransferHandler implements DragGestureListener, DragSourceListener, DropTargetListener {

  private ContainerElementTree tree;
  private DragSource dragSource; // dragsource
  private DropTarget dropTarget;
  private DefaultMutableTreeNode localNode = null;
  private Rectangle rect2D = new Rectangle();
  private int currentDragRow = -1;

  protected ContainerElementTreeTransferHandler(ContainerElementTree tree, int action) {
    this.tree = tree;
    dragSource = new DragSource();
    dragSource.createDefaultDragGestureRecognizer(tree, action, this);
    dropTarget = new DropTarget(tree, action, this);
  }


  /* ********  D R A G   S O U R C E   L I S T E N E R   M E T H O D S  ******** */
  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragSourceListener#dragDropEnd(java.awt.dnd.DragSourceDropEvent)
   */
  public void dragDropEnd(DragSourceDropEvent dsde) {
    if (dsde.getDropSuccess()) {
      try {
        Transferable transferable = dsde.getDragSourceContext().getTransferable();
        DefaultMutableTreeNode draggedNode = (DefaultMutableTreeNode) transferable.getTransferData(TransferableNode.NODE_FLAVOR);
        // remove after drag/drop - if we move it
        if (dsde.getDropAction() == DnDConstants.ACTION_MOVE && draggedNode != null) {
          DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
          treeModel.removeNodeFromParent(draggedNode);
          // mark modified
          JKeePass.application.setModified(true);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    localNode = null;
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragSourceListener#dragEnter(java.awt.dnd.DragSourceDragEvent)
   */
  public void dragEnter(DragSourceDragEvent dsde) {
    int action = dsde.getDropAction();
    if (action == DnDConstants.ACTION_COPY) {
      dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop);
    } else {
      if (action == DnDConstants.ACTION_MOVE) {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
      } else {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragSourceListener#dragOver(java.awt.dnd.DragSourceDragEvent)
   */
  public void dragOver(DragSourceDragEvent dsde) {
    int action = dsde.getDropAction();
    if (action == DnDConstants.ACTION_COPY) {
      dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop);
    } else {
      if (action == DnDConstants.ACTION_MOVE) {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
      } else {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragSourceListener#dropActionChanged(java.awt.dnd.DragSourceDragEvent)
   */
  public void dropActionChanged(DragSourceDragEvent dsde) {
    int action = dsde.getDropAction();
    if (action == DnDConstants.ACTION_COPY) {
      dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop);
    } else {
      if (action == DnDConstants.ACTION_MOVE) {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
      } else {
        dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragSourceListener#dragExit(java.awt.dnd.DragSourceEvent)
   */
  public void dragExit(DragSourceEvent dse) {
    dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
  }

  /* ********  D R A G   G E S T U R E   L I S T E N E R   M E T H O D S  ******** */  
  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DragGestureListener#dragGestureRecognized(java.awt.dnd.DragGestureEvent)
   */
  public void dragGestureRecognized(DragGestureEvent dge) {
    TreePath path = tree.getSelectionPath();
    if (path != null) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
      Transferable transferable = new TransferableNode(node);
      localNode = node;
      BufferedImage image = null;
      if (DragSource.isDragImageSupported()) {
        // getpathbounds of
        Rectangle pathBounds = tree.getPathBounds(path);
        // selectionpath
        JComponent lbl = (JComponent) tree.getCellRenderer().getTreeCellRendererComponent(tree, node, false,
            tree.isExpanded(path), ((DefaultTreeModel) tree.getModel()).isLeaf(path.getLastPathComponent()), 0, false);
        // setting bounds to lbl
        lbl.setBounds(pathBounds);
        // reference passing the label's ht and width
        image = new BufferedImage(lbl.getWidth(), lbl.getHeight(), java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE);
        // creating the graphics for buffered image
        Graphics2D graphics = image.createGraphics();
        // Sets the Composite for the Graphics2D context
        graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); //Sets
        lbl.setOpaque(false);
        lbl.paint(graphics); //painting the graphics to label
        graphics.dispose();
      }
      dragSource.startDrag(dge, DragSource.DefaultMoveNoDrop, image, new Point(0, 0), transferable, this);
    }
  }

  /* ********  D R O P   T A R G E T   L I S T E N E R   M E T H O D S  ******** */
  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent)
   */
  public void dragEnter(DropTargetDragEvent dtde) {
    dtde.acceptDrag(dtde.getDropAction());
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent)
   */
  public void dragExit(DropTargetEvent dte) {
    clearHighlight();
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent)
   */
  public void dragOver(DropTargetDragEvent dtde) {
    Point pt = dtde.getLocation();
    paintHighlight(pt);
    tree.autoscroll(pt);
    if (dtde.getCurrentDataFlavorsAsList().contains(TransferableNode.NODE_FLAVOR)) {
      TreePath pathTarget = tree.getPathForLocation(pt.x, pt.y);
      if (pathTarget != null) {
        DefaultMutableTreeNode newParentNode = (DefaultMutableTreeNode) pathTarget.getLastPathComponent();
        if (canLocalPerform(newParentNode, dtde.getDropAction())) {
          dtde.acceptDrag(dtde.getDropAction());
          return;
        }
      }
    } else if (dtde.getCurrentDataFlavorsAsList().contains(TransferableElement.ELEMENT_FLAVOR)) {
      dtde.acceptDrag(dtde.getDropAction());
      return;
    }
    dtde.rejectDrag();
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent)
   */
  public void dropActionChanged(DropTargetDragEvent dtde) {
    dtde.acceptDrag(dtde.getDropAction());
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent)
   */
  public void drop(DropTargetDropEvent dtde) {
    try {
      int action = dtde.getDropAction();
      clearHighlight();
      Transferable transferable = dtde.getTransferable();
      
      Point pt = dtde.getLocation();
      if (transferable.isDataFlavorSupported(TransferableNode.NODE_FLAVOR)) {
        TreePath pathTarget = tree.getPathForLocation(pt.x, pt.y);
        DefaultMutableTreeNode newParentNode = (DefaultMutableTreeNode) pathTarget.getLastPathComponent();
        // check localNode dependent first
        if (canLocalPerform(newParentNode, action)) {
          dtde.acceptDrop(action);
          DefaultMutableTreeNode draggedNode = (DefaultMutableTreeNode) transferable.getTransferData(TransferableNode.NODE_FLAVOR);
          if (executeDrop(draggedNode, newParentNode)) {
            dtde.dropComplete(true);
            return;
          }
        } else {
          dtde.rejectDrop();
        }
      } else if (transferable.isDataFlavorSupported(TransferableElement.ELEMENT_FLAVOR)) {
        // accept the drop
        dtde.acceptDrop(action);
        // get element
        BasicElement element = (BasicElement)transferable.getTransferData(TransferableElement.ELEMENT_FLAVOR);
        if (element != null) {
          // insert element into nodes element list
          TreePath pathTarget = tree.getPathForLocation(pt.x, pt.y);
          if (pathTarget != null) {
            DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) pathTarget.getLastPathComponent();
            if (parentNode != null) {
              ContainerElement containerElement = (ContainerElement)parentNode.getUserObject();
              if (containerElement != null) {
                // add to element list
                containerElement.addBasicElement(element);
                dtde.dropComplete(true);
                return;
              }
            }
          }
        }
      } else {
        dtde.rejectDrop();
      }
      dtde.dropComplete(false);
    } catch (Exception e) {
      System.out.println(e);
      dtde.rejectDrop();
      dtde.dropComplete(false);
    }
  }
  
  /**
   * canLocalPerform
   *
   * @param newParentNode
   * @param action
   * @return
   */
  private boolean canLocalPerform(DefaultMutableTreeNode newParentNode, int action) {
    // if we move we can not move to itself or decendent, only local
    if (action == DnDConstants.ACTION_MOVE && 
        (localNode != null && (localNode.isNodeDescendant(newParentNode)) || localNode.isRoot())) {
      return false;
    }
    return true;
  }

  /**
   * executeDrop
   *
   * @param draggedNode
   * @param newParentNode
   * @return
   */
  private boolean executeDrop(DefaultMutableTreeNode draggedNode, DefaultMutableTreeNode newParentNode) {
    // special for treenode, we remove always the parent
    draggedNode.removeFromParent();
    DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
    // insert node into parent on selected position
    treeModel.insertNodeInto(draggedNode, newParentNode,0);
    
    // mark modified
    JKeePass.application.setModified(true);
    // scroll to node
    TreePath treePath = new TreePath(draggedNode.getPath());
    tree.scrollPathToVisible(treePath);
    tree.setSelectionPath(treePath);
    return true;
  }

  /**
   * paintHighlight
   *
   * @param pt
   */
  private final void paintHighlight(Point pt) {
    int row = tree.getRowForLocation(pt.x, pt.y);
    if (row != currentDragRow && row >= 0) {
      clearHighlight();
      currentDragRow = row;
      rect2D = tree.getRowBounds(row);
      //tree.getGraphics().drawRect(rect2D.x, rect2D.y, rect2D.width-1, rect2D.height-1);
      BufferedImage image = new BufferedImage(rect2D.width, rect2D.height, java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE);
      Graphics2D graphics = image.createGraphics();
      graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); //Sets
      graphics.setColor(Color.BLUE);
      graphics.fillRect(0, 0, rect2D.width, rect2D.height);
      graphics.dispose();
      tree.getGraphics().drawImage(image, rect2D.x, rect2D.y, tree);
    }
  }
  
  /**
   * clearHighlight
   *
   */
  private final void clearHighlight() {
    if (rect2D != null) tree.paintImmediately(rect2D);
    currentDragRow = -1;
  }  

}